iT邦幫忙

2024 iThome 鐵人賽

DAY 10
0

元件類型都差不多以後
要進入另一個重點
我們用設定檔產生表單
還會需要幾個功能

  • 分頁
  • 檢核
  • 檢核未通過時阻止分頁

在這個情況下我們需要能處理檢核的元件
這樣似乎需要調整一下我們的架構
使用field-template的地方(目前是main)
需要知道這個欄位檢核有沒有通過
而且field-template目前是單一欄位
所以在main應該會需要有一個formGroup去包含全部的formControl
再用這個formGroup的invalid去判斷表單是不是通過驗證
這樣我們現在要做的調整有以下

  • 透過設定檔產生main的formGroup
  • 元件調整成用formControl判斷
  • main屬於應用層 如果之後要分頁的話 產生formGroup的邏輯要再多拆一層

今天的話應該現做元件的改寫

我們會先改main產生fromGroup
然後會調整field-template改傳遞

// main.component.ts

    fieldForm!: FormGroup; // 增加formGroup
    
    constructor(private fb: FormBuilder) { } // 使用formBuilder方便obj轉formGroup
    
    ngOnInit(): void {
        // 用reduce將name作為key值 defaultValue作為Value
        this.fieldObj = this.fieldSettings.reduce((acc: any, item) => {
            acc[item.name] = item.defaultValue || '';
            return acc;
        }, {});

        this.fieldForm = this.fb.group(this.fieldObj); // 增加此行將obj轉成formGroup
    }
    
    // html需要使用到
    getControl(columnName:string){
        return this.fieldForm.get(columnName) as FormControl
    }
    
// main.component.html
    <div [formGroup]="fieldForm">
      <div *ngFor="let fieldSetting of fieldSettings">
        <app-field-template [fieldSetting]="fieldSetting" [fieldObj]="fieldObj" 
        [control]="getControl(fieldSetting.name)">
        </app-field-template>
        <!-- {{ getControl(fieldSetting.name).value }} -->
      </div>
    </div>

從上面可以看到 我們增加了一個fieldForm來處理整個表單
直接透過fieldBuild將原本的fieldObj轉成fieldForm
這邊因為隔了太多層 所以決定直接把對應的formControl傳遞進去
傳遞到最裡面再處理
由於要傳對應的FormControl
所以這邊有多寫了一個getControl的方法去取得對應FormControl
用來測試的註解部分也稍微調整一下

field-template跟field的部分直接左手進右手出

// field-template.component.ts
    @Input() control!:FormControl;

// field-template.component.html 
// 增加傳遞control
  <app-field [fieldSetting]="fieldSetting" [fieldObj]="fieldObj" [control]="control"></app-field> 
  
// field.component.ts
    @Input() control!:FormControl;
    
// field.component.html
// 以base-text為例
    <app-base-text
      [fieldSetting]="fieldSetting"
      [fieldObj]="fieldObj"
      [formControl]="control"
    ></app-base-text>

然後關鍵就是在base元件的改動
現在base元件想要直接承接formContol來處理
所以需要implements ControlValueAccessor
首先要確保元件註冊NG_VALUE_ACCESSOR

@Component({
    selector: 'app-base-text',
    templateUrl: './base-text.component.html',
    styleUrls: ['./base-text.component.scss'],
    providers:[
        {
          provide: NG_VALUE_ACCESSOR,
          useExisting: forwardRef(() => BaseTextComponent),
          multi: true
        },]
})

然後implements ControlValueAccessor以後會有下面這些要處理

    writeValue(obj: any): void {
        throw new Error('Method not implemented.');
    }
    registerOnChange(fn: any): void {
        throw new Error('Method not implemented.');
    }
    registerOnTouched(fn: any): void {
        throw new Error('Method not implemented.');
    }
    setDisabledState?(isDisabled: boolean): void {
        throw new Error('Method not implemented.');
    }

writeValue是處理寫入數值
registerOnChange及registerOnTouched就是處理change跟touch的事件
第四個我們還沒用到

調整以後的base-text大概會是這樣

// base-text.component.html
<div>
  <input
    type="text"
    placeholder="{{ fieldSetting.placeholder || '文字輸入框' }}"
    [value]="value"
    (input)="onInput($event)"
  />
</div>
  
// base-text.component.ts
import { Component, forwardRef, Input } from '@angular/core';
import { FieldSetting } from '../field-setting.model';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

@Component({
    selector: 'app-base-text',
    templateUrl: './base-text.component.html',
    styleUrls: ['./base-text.component.scss'],
    providers:[
        {
          provide: NG_VALUE_ACCESSOR,
          useExisting: forwardRef(() => BaseTextComponent),
          multi: true
        },]
})
export class BaseTextComponent implements ControlValueAccessor {
    @Input() fieldSetting!: FieldSetting;
    @Input() fieldObj!: any;
    @Input() value: string = '';

    // 這些是 ControlValueAccessor 的實現
    onChange: any = () => { };
    onTouch: any = () => { };

    writeValue(value: any): void {
        this.value = value;
    }

    registerOnChange(fn: any): void {
        this.onChange = fn;
    }

    registerOnTouched(fn: any): void {
        this.onTouch = fn;
    }
    setDisabledState?(isDisabled: boolean): void {
        throw new Error('Method not implemented.');
    }

    onInput(event: Event): void {
        const input = event.target as HTMLInputElement;
        this.onChange(input.value);
        this.onTouch();
    }
}

原本的valueChange調整為onInput並且綁訂在input事件上

這邊因為main.component以及base元件的部分都有使用到ReactiveFormsModule
記得在app.module.ts以及element.modul.ts內引入

然後到main把註解拿掉測試看看有沒有問題
接著嘗試也用同樣方法改動其他base的時候
發現有其他問題
那我們留著明天一併處理好了 今天就先調整base-text

今日程式:day10


上一篇
第9天 多選checkbox元件
下一篇
第11天 要改的東西太多了 不如就繼承吧
系列文
簡單的事 最困難-Angular動態Form元件30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言